Skip to content

fix(create-app): design contracts for local Docker start failure and transient UVE 403 (#37262) - #37264

Merged
fmontes merged 26 commits into
mainfrom
issue-37262-create-app-docker-uve
Sep 3, 2026
Merged

fix(create-app): design contracts for local Docker start failure and transient UVE 403 (#37262)#37264
fmontes merged 26 commits into
mainfrom
issue-37262-create-app-docker-uve

Conversation

@fmontes

@fmontes fmontes commented Aug 28, 2026

Copy link
Copy Markdown
Member

PR 2 of 2 for #37262, stacked on #37263 (the spec).

The problem

npx @dotcms/create-app --local never starts dotCMS — it races Postgres and dies. Hand-starting corrupts the install, every UVE call 403s forever, and the CLI exits with nothing scaffolded.

flowchart TD
    A["dotcms starts before Postgres accepts connections"] -->|no restart policy| B["exits, stays dead"]
    B --> C["user hand-starts it from Docker Desktop"]
    C --> D["starter import left incomplete"]
    D --> E["every Apps API call 403s — permanently"]
    E --> F["CLI exits 1 · empty directory · working token discarded"]

    FIX1["**Fix 1** — compose gates dotcms on db + opensearch<br/>healthy, with a restart policy"]
    FIX2["**Fix 2** — UVE failure is non-fatal:<br/>project still scaffolds, .env still written"]

    FIX1 -.->|removes| A
    FIX2 -.->|removes| F

    style FIX1 fill:#dff0e8,stroke:#146046,color:#000
    style FIX2 fill:#dff0e8,stroke:#146046,color:#000
    style F fill:#fbe7e4,stroke:#a8281f,color:#000
Loading

The fix

One compose file, owned by the CLI. It ships inside the package instead of being fetched from main, so dotcms can be gated on db and opensearch reporting healthy, given restart: unless-stopped, and health-checked on /dotmgt/livez. The shared single-node-demo-site example is untouched. This alone removes the 403, because it removes the interrupted boot that causes it.

A failed UVE call no longer destroys the run. Two duplicated exit-on-failure call sites collapse into one configureUVE() that never calls process.exit. The 403 guidance differs by path: recreate the local stack, or check the token's permissions on your own server — opposite advice for the same status code.

Nothing successful is discarded. An exit handler writes .env and reports the connection details on every terminal path, including the 14 process.exit sites a finally cannot reach.

Recovery is unblocked. A dotCMS already on 8082 can be reused or replaced from the prompt instead of aborting; a failed npm install now actually reports failure; the compose file is never stranded in the parent directory.

The wait is legible. Compose progress is streamed with an elapsed-time ticker, and readiness moves to /dotmgt/readyz.

Verification

144 Jest tests. Two gates now run in CI via nx affected -t test: verify-compose-static (compose shape, no Docker) and verify-package (the asset is in dist/ and in npm pack output — asserting the artifact, not the manifests).

Verified end to end against a real stack: cold start healthy unaided in 60s, self-exit restarted by the policy, 8090 refused on the LAN address, and both 403 messages exercised against a stub.

One caveat. The spec's root cause 2 could not be independently reproduced: deleting the site's 27 permission rows still returned 200, because userDoesNotHaveAccess() short-circuits on user.isAdmin(). The fix is unaffected — it removes the crash, and the 403 handling is verified — but #37268 may be chasing the wrong data.

Checklist

  • Tests — 144 Jest tests, written and confirmed failing before implementation (constitution Principle V). Cold-start behaviour is covered by verify-cold-start.sh; the full fault-injection E2E suite belongs to e2e: Add E2E test suite for @dotcms/create-app CLI #35096
  • Translations — n/a
  • Security Implications Contemplated — the management port is authorized by arrival port with no credential check, so it is bound to 127.0.0.1 rather than the wildcard, and a test asserts it is refused on the LAN address

Additional Info

No Java, no com.dotmarketing.*, no DB/ES/REST contract change — not rollback-unsafe. Per ADR-0019 this ships in a dotCMS release rather than a standalone SDK publish.

Refs #37262

This PR fixes: #37262

This PR fixes: #37262

@fmontes fmontes changed the title docs(create-app): add design contracts and data model for #37262 fix(create-app): design contracts for local Docker start failure and transient UVE 403 (#37262) Aug 28, 2026
@claude

claude Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Claude finished @fmontes's task in 3m 19s —— View job


Code review — #37264

Reviewed the current head (c42dbbb5) against the prior findings and scanned the new modules for regressions. All previously reported issues are resolved and I found no new blocking issues.

Resolved

  • axios proxy-credential leak (2× semgrep High) — axios is gone. src/utils/http.ts is a native-fetch client; the fetch spec strips Authorization on cross-origin redirect, which is exactly the protection axios's Node adapter lacked. It was the only lib depending on axios (fa551f0b).
  • withComposeFileMovedAside stranded the compose file on failure / clobbered a parent docker-compose.yml — now moves aside into a per-call mkdtemp under os.tmpdir() and restores in finally; the scaffold-failure path throws instead of process.exit, so finally actually runs (src/utils/compose-move.ts, src/index.ts:786-794).
  • applyStarterUrl treated $ in the URL as a replacement pattern — now uses a replacer function, so $1/$& in a --starter URL are passed verbatim (src/utils/starter-url.ts:41-44).
  • Stale ports 9200/9600 in the conflict check and diagnosticsREQUIRED_PORTS is now 8082/8443/8090 and the diagnostics text matches (src/utils/ports.ts:65-69, src/utils/index.ts:660-662).

Notes (non-blocking)

  • UVE failure is correctly non-fatal on both paths: configureUVE returns a UveOutcome, callers spinner.warn and continue (src/index.ts:489-494, 246-260). Retries are spent on 5xx only; 403 is terminal with mode-specific guidance — good.
  • Recovery contract holds: flushRecoverableState() in displayFinalSteps claims reporting (reported = true) before the summary prints, so the process.on('exit') fallback can't double-print, and a throw after the token is issued still surfaces it (src/index.ts:729, src/exit-state.ts:174-190).
  • Readiness probe wires acceptAnyStatus: true so a 503 from /dotmgt/readyz is treated as "still starting" data rather than a thrown error (src/index.ts:409, src/utils/readiness.ts).

No issues found.

@fmontes
fmontes force-pushed the issue-37262-create-app-docker-uve branch from 244a69d to 38e8ae1 Compare August 28, 2026 11:59
@fmontes
fmontes force-pushed the issue-37262-create-app-docker-uve branch 4 times, most recently from 4877b92 to c3243d6 Compare August 28, 2026 20:38
@github-actions github-actions Bot added Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries labels Aug 28, 2026
Comment thread core-web/libs/sdk/create-app/src/uve/configure-uve.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/uve/configure-uve.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/utils/index.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/index.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/uve/configure-uve.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/uve/configure-uve.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/utils/index.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/index.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/uve/configure-uve.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/uve/configure-uve.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/utils/index.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/index.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/uve/configure-uve.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/uve/configure-uve.ts Outdated
Comment thread core-web/libs/sdk/create-app/src/utils/index.ts Outdated
fmontes and others added 12 commits September 3, 2026 17:30
…37262)

Found by running the CLI end to end (T054), which turned out to be automatable
after all - every prompt has a flag, and the no-TTY path is one the port-reuse
logic already handles by design, so `node index.js my-app -d <dir> -f nextjs
--local` runs the whole thing unattended.

The .env written for AC-004 used variable names I invented rather than the ones
the project reads. It emitted DOTCMS_AUTH_TOKEN where Next.js reads
NEXT_PUBLIC_DOTCMS_AUTH_TOKEN, and omitted NEXT_PUBLIC_DOTCMS_MODE entirely. The
file looked entirely plausible and `npm run dev` would have failed to
authenticate with nothing explaining why - the same failure shape as the bug this
issue is about, a green signal that does not mean what it claims.

The unit test did not catch it because it asserted the file "contains the host,
token and siteId", which a wrong-named file satisfies perfectly.

Two more cases were wrong for the same reason: Astro reads PUBLIC_* with a
different variable set, and Angular has no dotenv file at all - it reads a
TypeScript `environment` object, so writing .env there was cargo-culting. The
`framework` field was already on RecoverableState and simply never used.

Root cause was duplication: getEnvVariablesForNextJS had defined the real names a
few hundred lines away in the same package. There is now a single owner,
getEnvFileSpec(), used by BOTH the printed block and the written file, so they
cannot drift apart again. It returns filename: null for frameworks with no dotenv
file, which is what makes the Angular case correct rather than merely skipped.

The spec now pins all three shapes, including a negative assertion that the bare
DOTCMS_AUTH_TOKEN name never appears on its own.

Also reworded the final steps, which told the user to `touch .env` moments before
the CLI wrote that same file - instructions contradicting an action the tool was
about to take.

Verified by re-running the same end-to-end path: exit 0 in ~100s, project
scaffolded, all four NEXT_PUBLIC_* variables present and correct, compose file
back in the project directory with no orphan in the parent, and UVE configured
with no 403 - which is the causal chain behaving as diagnosed, since a clean boot
is exactly the case that should not 403.

128 tests, 10 suites; build and lint clean.

Refs #37262

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…37262)

Two defects in the wiring, both found by running the CLI against a real running
instance (T054 step 5b) and neither reachable by the unit tests.

Reuse could never trigger. resolvePortConflict required 8082 to be the ONLY busy
port, but a running stack publishes 8082 AND 8443, so the condition could not
occur in practice and reproduction step 6 stayed broken. Twelve tests passed
because the fixture used a single busy port - a shape no real instance produces.
The fixtures are now derived from what the bundled asset actually publishes and
named A_REAL_RUNNING_STACK, since the abstraction was the bug.

Reuse was then ignored anyway. The decision was computed and used only to choose a
spinner message; the CLI went on to write a compose file and run `up` regardless,
which failed with "Bind for 0.0.0.0:8082 failed: port is already allocated". Reuse
that still provisions is not reuse. Provisioning is now skipped when reusing,
which is the whole point of the decision.

REQUIRED_PORTS still listed 9200 and 9600, inherited from the shared compose
example the CLI no longer downloads. The bundled asset publishes neither -
OpenSearch has no ports section - so the CLI refused to run for anyone with their
own OpenSearch on 9200, over a conflict that cannot happen. It now checks 8082,
8443 and 8090, and a spec parses the asset and asserts the two lists agree so they
cannot drift again.

Verified end to end against a real instance: the CLI reuses a running stack,
skips provisioning, and completes (exit 0, scaffolded, .env written). A separate
run confirmed the opposite branch too - an instance that is up but cannot issue a
token is correctly refused rather than adopted, with "Something is listening on
8082, but it did not answer as a usable dotCMS".

Both 403 messages are now verified against real code rather than mocks, using a
stub server whose UVE endpoint always 403s:

  local  - exit 0, scaffolded, .env written; says unrecoverable, gives
           `docker compose down -v`, cites #37268, WITHHOLDS the manual guide.
  remote - exit 0, scaffolded, .env written; names the permission problem, the
           site and the app key, OFFERS the guide, and leaks neither `down -v`
           nor #37268.

In both cases the UVE endpoint was hit exactly ONCE - no POST after a 403, no
retry, no poll (AC-005).

132 tests, 10 suites; build and lint clean.

Refs #37262

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The prompt said:

  ? A dotCMS instance is already running on port 8082. What would you like to do?
    > Reuse the running instance
      Abort so I can check what it is

That states a fact and then abandons the user. It never says WHAT is running, so
there is no basis for choosing - "is that my last run, or the instance I am using
for other work?" - and the only two answers are take it or quit. Anyone who did
not want that instance had to leave the CLI and work out the docker incantation
themselves.

It now says what it found and offers a way through:

  ⚠  Found a dotCMS already running at http://localhost:8082
     Docker project "my-app" · Up 8 minutes (healthy)

  ? How would you like to continue?
  > Use this instance for my project
      Fastest. Keeps its existing content.
    Replace it with a clean instance
      Stops it and DELETES its data, then starts fresh.
    Cancel
      Change nothing and exit.

describePortOwner() reads the compose labels off whatever publishes the port, so
the project name, health and uptime are shown before the question is asked.

Replace runs `docker compose -p <project> down -v` and then provisions fresh. The
-v is the point: keeping the volumes keeps the corruption, so a bricked instance
would come back just as broken (#37268). This turns the documented recovery from
something the CLI tells you to go and do into something it can do.

Two safety properties:

* Replace is only offered when a compose project owns the port. A container
  started outside compose has no project label and is not ours to destroy, so the
  option is withheld rather than offered and then failed.
* Replace is NEVER selected non-interactively. Destroying an instance is not
  something to infer from the absence of a TTY; a scripted run still auto-reuses
  with a printed notice, per decision D3.

Verified against a real running stack: owner detection reports
`Docker project "my-app" · Up 8 minutes (healthy)`, and a non-interactive run
still prints its notice, reuses, and completes with .env written and exit 0.

136 tests, 10 suites; build and lint clean.

Refs #37262

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The prompt printed its question and nothing under it:

  ? How would you like to continue?

It used `type: 'list'`. This package is on inquirer 13, which is built on
@inquirer/prompts, where the single-choice prompt is `select` - `list` is the
inquirer 8/9 name. An unregistered type renders the MESSAGE and silently renders
no choices: nothing throws, nothing warns, and the user is left looking at a
question with no answers.

It was the only `type: 'list'` in the package. Every other prompt in asks.ts -
askCloudOrLocalInstance, askFramework, prepareDirectory - already used `select`,
`input`, `password` or `confirm`, and was visibly working in the same terminal
session. Comparing against the neighbouring prompt that worked would have found
this immediately; instead it survived a round of fixes aimed at embedded newlines
and nested chalk in the choice labels, which were real smells but not the cause.

Proven by holding everything else constant and changing only the type:

  type=list    choice text rendered: 0
  type=select  choice text rendered: 2

A spec now guards it, and was confirmed to fail when the bug is reintroduced. It
asserts asks.ts never uses type: 'list', and that every prompt type used is one
this inquirer version actually registers. This is worth a test because the
failure is silent and looks like a styling problem rather than a wrong prompt
type, so it costs far more to diagnose than to prevent.

The choice labels are also cleaned up as part of this: single-line names with the
hint moved to `description`, which @inquirer/select 5.2.1 supports and renders
under the highlighted option, instead of a `\n` inside the name and a nested
chalk.gray inside a label inquirer re-styles when highlighting.

Confirmed working in a real terminal by the reporter.

138 tests, 11 suites; build and lint clean.

Refs #37262

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A successful run ended by printing the API token twice - once in a "copy this
block and paste it into your .env" section, and again in the recovery block -
while having already written that exact file itself. So it duplicated a JWT into
scrollback and told the user to redo work that was done.

Now the CLI owns the file and says so:

  Wrote .env with your dotCMS connection details.
    host    : http://localhost:8082
    site id : 48190c8c-42c4-46af-8d1a-0cd5db894797
    token   : stored in .env

The token is no longer echoed when it was successfully written. It is safe on
disk, and a JWT in terminal scrollback and CI logs buys nothing. The "create your
environment file" and "add your dotCMS configuration" steps are gone from the
Next.js and Astro flows, and the remaining steps renumber to 1-4: cd, npm run dev,
open the browser, edit the page.

The guarantee behind contract X1 is unchanged - no successful state is ever
discarded - but it is now satisfied by the file rather than by the terminal. When
nothing could be written the full block still prints, including the token, because
then the terminal IS the only place the run survives. Three cases take that path:
the framework has no dotenv file, a .env already exists and is left untouched, or
the write failed.

Angular deliberately keeps its paste block. It has no .env - it reads a TypeScript
environment object - so getEnvFileSpec writes nothing for it and the user really
does have to paste into the environment files. The exit handler prints the full
block for Angular for the same reason.

Also dropped `siteId` and `token` from finalStepsForNextjs and finalStepsForAstro,
which no longer render them.

140 tests, 11 suites; build and lint clean.

Refs #37262

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A successful run ended with the summary, and then a second block after it:

    📋 Next Steps:
    ...
    💬 Community: https://community.dotcms.com

    Wrote .env with your dotCMS connection details.
      host    : ...

That was structural, not cosmetic. The exit handler runs on process 'exit', so
anything it prints necessarily lands AFTER everything else - it could only ever
append to the summary that was supposed to contain it.

flushRecoverableState() lets the success path do the env write and claim the
reporting, so the details render inside the Next Steps block, above step 1:

    📋 Next Steps:

       ✔ Your dotCMS credentials are already in .env
         host    : http://localhost:8082
         site id : 48190c8c-42c4-46af-8d1a-0cd5db894797

    1. Navigate to your project:
    ...

The exit handler then stays silent, because the state has already been surfaced.
It remains the fallback for every path that never reaches the summary - the 14
process.exit sites, an unexpected throw - which is the whole point of contract X1
and is unchanged. A spec pins both halves: silent after a flush, still speaking
without one.

The same renderer covers the case where no file was written (Angular has no
dotenv file, or a .env already exists, or the write failed). It then prints the
values to paste, in the same place, rather than in a separate trailing block.

144 tests, 11 suites; build and lint clean. Placement verified in the shipped
bundle: the summary renders between the "Next Steps" heading and step 1.

Refs #37262

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
)

Closes the gap @nicobytes raised on #37263: AC-013 is the one criterion the spec
itself calls "the most likely way to break the release", and nothing actually
enforced it.

What existed asserted the wrong thing. packaging.spec.ts reads package.json and
project.json from the SOURCE TREE, and verify-cold-start.sh --static resolves the
asset relative to src/. Both pass identically whether or not the file ever ships.
A wrong `output:` in the esbuild assets entry satisfies every one of them and
still publishes a package with no compose file - and then every local-Docker run
fails at its first step, which is precisely what AC-013 exists to prevent. The
packaging had in fact been verified once, by hand, with npm pack --dry-run; there
was no gate.

scripts/verify-package.sh asserts the artifact instead:

  * the compose asset is in dist/libs/sdk/create-app at the path the CLI resolves
    at runtime (resolveComposeSource walks up from the bundle entry, so it must
    sit beside index.js exactly as it does in the source tree), and
  * npm pack --dry-run lists it in the tarball contents.

Those are two independent failures and both are checked, because either alone
ships a broken package: esbuild copies the file but `files` omits it, or `files`
is right and the copy never happened.

Confirmed to FAIL on both modes rather than assumed: removing dist/assets fails
both checks; restoring the asset and reverting `files` to its pre-fix
["*.js", "README.md"] passes the first and fails the second, naming package.json.

Wired as the nx target `verify-package` with dependsOn build, deliberately NOT
folded into `test` - tests must stay fast and must not require a build.

Also tags T052 with AC-011. That criterion (8090 answers on loopback, refused on
the LAN address) was already enforced by verify-cold-start.sh's T007 check but
referenced by no task, so the only security criterion in the list read as
uncovered. Acceptance-criteria coverage is now 13/13.

144 tests, 11 suites; build, lint and both verification scripts clean.

Refs #37262, #37263

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Neither script ran in CI. The Maven build drives nx with exactly four
invocations - `nx affected -t lint`, `nx format:check`, `nx run-many -t build`,
`nx affected -t test` - so `verify-package` was never reached, and
verify-cold-start.sh was not an nx target at all. Both were manual, which for
AC-013 in particular means the criterion the spec calls "the most likely way to
break the release" had a gate nobody ran.

Both now hang off this project's `test` target, so `nx affected -t test` picks
them up. That keeps the change inside libs/sdk/create-app/project.json rather
than editing core-web/pom.xml, which every project in the monorepo shares.

New target `verify-compose-static` runs verify-cold-start.sh --static: the
config-only half, no Docker, ~0.3s. It is what catches a reformatted
CUSTOM_STARTER_URL line silently breaking --starter for every installed CLI. The
runtime half (cold start, restart recovery, LAN exposure) needs a real daemon and
a multi-minute starter import and stays manual.

Both targets get `cache: true` with narrow inputs - the compose asset, the
manifests and the scripts themselves - so they do not re-run on unrelated edits.

Measured: 4s cold, 1s warm, against 144 Jest tests. This reverses the "keep tests
fast and buildless" call made when verify-package was added; that rule earns its
keep on a large Angular library, not on a CLI whose build is 1.3s.

Verified the chain actually fails rather than assumed: reverting `files` in
package.json to its pre-fix ["*.js", "README.md"] fails the packaging gate and
aborts the run before Jest executes.

Refs #37262, #37263

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
semgrep-dotcms flagged axios on this PR with several High findings, all the same
root cause: axios's Node adapter does not clear `Proxy-Authorization` when a
request that went through an authenticated proxy is redirected to a target that
does not use that proxy, leaking the proxy credentials to the redirect origin.

Bumping to axios 1.20.0 would close those CVEs. Removing axios closes the class,
and the case for removing it here is unusually strong:

* create-app was the ONLY consumer of axios in the workspace - no other lib or app
  imports it.
* It sat in esbuild's `external` list, so it was a real runtime install for
  everyone running `npx @dotcms/create-app`, not just a build-time dependency.
* compose-source.ts in this same package already used native fetch, so the
  inconsistency was ours.
* Node >= 22.22.3 is required here (.nvmrc), where fetch is stable.

New `src/utils/http.ts` is the single owner of HTTP: timeout via AbortController
(fetch has none of its own, so a dead instance used to hang the CLI), best-effort
JSON parsing that tolerates a 204 or a plain-text body, and an HttpError that
keeps axios's `error.response.status` shape so configureUVE's statusOf() and the
retry classifier keep working unchanged. Throw-on-non-2xx is the default because
that is what every call site expected; `acceptAnyStatus` covers the readiness
probe, where a 503 is data rather than a failure.

Worth noting the fetch spec requires stripping `Authorization` on a cross-origin
redirect - the protection axios's Node adapter was missing.

Migrated: api/index.ts, uve/configure-uve.ts, utils/index.ts (fetchWithRetry),
utils/fetch-retry.ts and index.ts. axios is gone from the published dependencies
and from the esbuild external list; the shipped bundle contains zero references.
The pnpm lockfile is unaffected - libs/sdk/create-app is not a separate importer,
so its package.json is the published manifest rather than an install manifest.

configure-uve.spec mocks the http module rather than fetch, so its cases stay
about the contract (probe once, retry 5xx only, mode-dependent guidance) while
the new http.spec covers the transport - 15 cases including timeout, transport
failure, non-JSON bodies and the 2xx boundary.

Verified over real HTTP end to end, not just against mocks: a run against a stub
whose UVE endpoint 403s makes all four calls through native fetch (health, token
POST, site GET, UVE probe), exits 0, scaffolds, writes .env, and prints the
remote-mode 403 guidance without leaking `docker compose down -v`.

159 tests, 12 suites; build, lint and both verification gates clean.

Refs #37262, #37264

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…37262)

The README was updated when the compose file became bundled, then drifted: three
later changes invalidated parts of it and it was never revisited. Two of those
were outright wrong rather than merely stale.

Wrong:

* "Validates required ports: 8082, 8443, 9200, 9600". The CLI checks 8082, 8443
  and 8090. The 9200/9600 pair was inherited from the shared compose example the
  CLI no longer downloads.
* The published-ports table listed "9200, 9600 — all interfaces" for OpenSearch.
  The bundled stack publishes NOTHING for Postgres or OpenSearch; they are only
  reachable inside the compose network. Readers were being told to expect a port
  conflict that cannot happen, and the table now says so explicitly, since
  "can I run my own OpenSearch alongside this?" is the obvious question.

Stale:

* Both flows ended with "prints framework-specific env setup instructions". The
  CLI writes .env itself now.
* Nothing described the port-conflict prompt, which is a destructive choice
  (Replace removes volumes) and needs documenting before someone meets it.
* Local flow claimed a generic "waits for local health check"; it waits on
  /dotmgt/readyz with /api/v1/appconfiguration as fallback.
* UVE configuration was presented as a required step in both flows. It is
  optional - failure warns and continues, which is the entire point of the fix.
* Troubleshooting told the reader to go stop the process on 8082 by hand. The CLI
  now offers to reuse or replace it.
* Requirements said "Node.js + npm"; native fetch means Node 22.22.3+ is required
  and the .nvmrc already pins it.
* Dev commands used yarn; this workspace is pnpm.

Added: "If dotCMS is already running" (the prompt, what Replace destroys, why it
is withheld when no Compose project owns the port, and the non-interactive
behaviour) and "Your .env" (per-framework variable names, that an existing file
is never overwritten, and that Angular has none). Also documents
`pnpm nx verify-package sdk-create-app`.

Every remaining factual claim was cross-checked against the source rather than
re-read: port list, readiness endpoints, and the DOTCMS_COMPOSE_URL escape hatch.

Refs #37262

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`nx format:check` failed CI on five files this branch touched: project.json,
src/api/index.ts, src/asks.spec.ts, src/exit-state.spec.ts and
src/utils/ports.ts. Formatting only — no behaviour change. 159 tests, lint and
both verification gates still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
)

`process.exit` skips `finally`, so the scaffold-failure path bypassed the very
`withComposeFileMovedAside` guard that AC-008 added — it now throws, which
reaches both the `finally` and the outer catch (still exit 1).

The holding spot for the compose file moves to a private `mkdtemp` dir. It was
the parent, i.e. the user's cwd, with `overwrite: true` — so scaffolding next
to your own docker-compose.yml destroyed it, unrecoverably, since `finally`
then moved ours into the project.

`applyStarterUrl` uses a replacer function: as a replacement string, `$1`/`$&`/
`$'` in a `--starter` URL were expanded instead of written.

Docker troubleshooting listed ports 9200/9600, which this branch dropped from
REQUIRED_PORTS, and omitted 8090.

Both new specs verified Red against the old code first. 166 tests (was 159).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wezell
wezell force-pushed the issue-37262-create-app-docker-uve branch from 19fbf84 to 59890cb Compare September 3, 2026 21:30
wezell
wezell previously approved these changes Sep 3, 2026

@wezell wezell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was waiting for dotbot to respond, but I'll approve instead

…ed (#37262)

Ran the real `--local` flow with the git clone rigged to fail, which is the
scaffold-failure path AC-008 is about and the one no test reached. It passes:
the compose file returns to the project, a pre-existing parent
docker-compose.yml stays byte-identical, and no holding dir is left behind.
Three things the run found that unit tests could not:

`throw created.val` rather than a new Error carrying the spinner's own text —
the outer catch prints whatever it is handed, so the previous version said
"Failed to scaffold frontend project (nextjs)." twice. The underlying error
names the likely causes instead.

`moveDockerComposeOneLevelUp` / `moveDockerComposeBack` are gone from
src/git/index.ts. Dead code with no callers, not exported from the entry — but
they did the unguarded parent-directory move with no `finally`, i.e. both
defects review just found, one import away from returning.

`getSafeErrorDetails` no longer falls back to "Axios request failed"; this
package dropped axios earlier on this branch.

166 tests, lint, build and format:check clean. Stack and volumes torn down.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

dotbot code review:

  • Reviewer: meta/muse-spark-1.3 (medium)
  • Overall: patch is correct
  • New findings this run: 0
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 0

No P0/P1 bug introduced by this patch is provable from repo code. The new fetch client, readiness fallback, port-reuse, compose-move, and exit-state paths are consistent with callers and contracts.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · meta/muse-spark-1.3 · medium

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

dotbot code review:

  • Reviewer: ~z-ai/glm-latest (medium)
  • Overall: patch is correct
  • New findings this run: 0
  • Prior unresolved dotbot findings still relevant: 0
  • Active findings total: 0

Verified against the repo: http.ts preserves the axios error shape (.response.status, .code) read by api/index.ts and fetchWithRetry; configure-uve.ts's single GET probe, 5xx-only retries, and non-fatal outcomes match the specs/37262 contracts and the AppsResource SecretForm payload ({configuration: {hidden, value}} → Input with value/hidden); REQUIRED_PORTS (8082/8443/8090) matches the bundled assets/docker-compose.yml publishing; the exit-state handler, compose-move finally, and port-reuse paths are consistent with their callers in src/index.ts. No P0/P1 bug introduced by this patch is provable from repository code.

Tip: comment with "/dotbot address comments" to attempt automated fixes for unresolved review threads.

reviewed by dotbot · ~z-ai/glm-latest · medium

@dotCMS-Machine-User dotCMS-Machine-User left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ dotbot review: all reviewer models (meta/muse-spark-1.3, ~z-ai/glm-latest) agree — patch is correct.

approved automatically by dotbot

@fmontes
fmontes added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 788795e Sep 3, 2026
59 checks passed
@fmontes
fmontes deleted the issue-37262-create-app-docker-uve branch September 3, 2026 23:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries PR : dotbot review Trigger dotbot AI code review on this PR

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

create-app: local Docker run never starts dotCMS, then a transient UVE 403 aborts the CLI and discards the project

3 participants